/*
 * Copyright 2008 FBK (http://www.fbk.eu/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fbk.it.hlt.jlsi;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.fbk.it.hlt.jlsi.data.DenseTextMatrixFileReader;
import org.fbk.it.hlt.jlsi.data.DenseTextVectorFileReader;

import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix1D;

/**
 * Term similarity in the domain VSM.
 * 
 * @author Claudio Giuliano
 * @version %I%, %G%
 * @since 1.0
 * @see DomainTermSimilarity
 */
public class DomainTermSimilarity extends AbstractTermSimilarity {
	/**
	 * Define a static logger variable so that it references the Logger instance
	 * named <code>DomainTermSimilarity</code>.
	 */
	static Logger logger = Logger.getLogger(DomainTermSimilarity.class
			.getName());

	/**
	 * Matrix created by SVD
	 */
	private DoubleMatrix2D Uk;

	/**
	 * Matrix created by SVD
	 */
	private DoubleMatrix1D Sk;

	/**
	 * Diagonal matrix such that each element is the inverse document frequency
	 * of a term.
	 */
	private DoubleMatrix1D Iidf;

	//
	private TermIndex termIndex;

	//
	private DocumentIndex documentIndex;

	//
	private double threshold;

	//
	private int size;

	/**
	 * Constructs a <code>DomainTermSimilarity</code> object.
	 */
	public DomainTermSimilarity(File UtFile, File SFile, File rowFile,
			File colFile, File dfFile, double threshold, int size)
			throws IOException {
		this.threshold = threshold;
		this.size = size;
		termIndex = TermIndex.getTermIndex();
		termIndex.read(new FileReader(rowFile));
		documentIndex = DocumentIndex.getDocumentIndex();
		documentIndex.read(new FileReader(colFile));
		// number of documents
		int l = documentIndex.documentSet().size();
		// document frequency
		Vocabulary voc = new Vocabulary();
		voc.read(new FileReader(dfFile));
		createIdf(voc, l);
		DenseTextVectorFileReader SVectorFileReader = new DenseTextVectorFileReader(
				SFile);
		Sk = (DoubleMatrix1D) SVectorFileReader.read();
		DenseTextMatrixFileReader matrixFileReader = new DenseTextMatrixFileReader(
				UtFile);
		Uk = (DoubleMatrix2D) matrixFileReader.read();
		// System.out.println(Uk);
		// Multiply Uk for Sk
		rescale();

		//
		normalize();
		idf();
		// termByTerm(voc);
	} // end constructor

	//
	private void createIdf(Vocabulary voc, int l) {
		// tf è la frequenza di un termine, mentre l è il numero totale di
		// documenti
		Iidf = new DenseDoubleMatrix1D(voc.entrySet().size());
		// logger.debug("Iidf.size: " + Iidf.size());
		// iterates over the types
		Iterator it = voc.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry me = (Map.Entry) it.next();
			String term = (String) me.getKey();

			Vocabulary.TermFrequency tf = (Vocabulary.TermFrequency) me
					.getValue();
			int index = termIndex.getIndex(term);

			if (index == 10167)
				logger.debug(index + ", " + term + " " + l + " / " + tf.get()
						+ " = " + Math.log((double) l / tf.get()));

			// log ( l / df)
			Iidf.setQuick(index, Math.log((double) l / tf.get()));
			// Iidf.setQuick(index, 1.0);
		} // end while
	} // end createIdf

	/**
	 * Multiply Uk for Sk.
	 */
	private void rescale() {
		logger.debug("rescale: Uk[" + Uk.rows() + " X " + Uk.columns()
				+ "] * Sk[" + Uk.columns() + " X " + Uk.columns() + "]");

		// Uk.col = l
		for (int i = 0; i < Uk.columns(); i++) {
			DoubleMatrix1D col = Uk.viewColumn(i);
			// logger.info(col);

			for (int j = 0; j < col.size(); j++) {
				// logger.info(j + " " + col.getQuick(j) + " * " +
				// Sk.getQuick(i));
				col.setQuick(j, col.getQuick(j) * Math.sqrt(Sk.getQuick(i)));
			} // end for j
		} // end for i
	} // end rescale

	/**
	 *
	 */
	private void normalize() {
		logger.debug("normalize: IN[" + Iidf.size() + " X " + Iidf.size()
				+ "] * Uk[" + Uk.rows() + " X " + Uk.columns() + "]");

		// Uk.rows = N
		for (int i = 0; i < Uk.rows(); i++) {
			DoubleMatrix1D row = Uk.viewRow(i);

			double sum = 0;
			for (int j = 0; j < row.size(); j++)
				sum += Math.pow(row.getQuick(j), 2);

			// normalization
			sum = Math.sqrt(sum);
			for (int j = 0; j < row.size(); j++) {
				// logger.debug(row.getQuick(j) + " / " + sum + " = " +
				// (row.getQuick(j) / sum));
				row.setQuick(j, row.getQuick(j) / sum);
			}
		} // end for i

	} // end normalize

	/**
	 *
	 */
	private void idf() {
		logger.debug("idf: Iidf[" + Iidf.size() + " X " + Iidf.size()
				+ "] * Uk[" + Uk.rows() + " X " + Uk.columns() + "]");

		// Uk.rows = N
		for (int i = 0; i < Uk.rows(); i++) {
			DoubleMatrix1D row = Uk.viewRow(i);

			// I^idf X (Uk * Sk)
			for (int j = 0; j < row.size(); j++)
				row.setQuick(j, row.getQuick(j) * Iidf.getQuick(i));

		} // end for i

	} // end normalize

	//
	public void termByTerm(Vocabulary voc) throws TermNotFoundException {
		Object[] termArray = voc.keySet().toArray();
		ScoreTermMap[] scoreArray = new ScoreTermMap[termArray.length];

		logger.info(termArray.length + " terms to be compared");

		for (int i = 0; i < termArray.length; i++)
		// for (int i=0;i<100;i++)
		{
			String t = (String) termArray[i];
			System.out.println(i + " " + t);
			ScoreTermMap map = compareAll(t);
		} // end for i
	} // end termByTerm

	/**
	 *
	 */
	public DoubleMatrix1D getVector(String term) {
		int i = termIndex.getIndex(term);
		// logger.info(term + " " + i);

		if (i == -1)
			return null;

		return Uk.viewRow(i);
	} // end getVector

	/**
	 *
	 */
	public double compare(String t1, String t2) throws TermNotFoundException {
		int i1 = termIndex.getIndex(t1);

		if (i1 == -1)
			throw new TermNotFoundException(t1);

		DoubleMatrix1D x1 = Uk.viewRow(i1);
		// logger.debug(t1 + "\n" + x1);

		int i2 = termIndex.getIndex(t2);

		if (i2 == -1)
			throw new TermNotFoundException(t2);

		DoubleMatrix1D x2 = Uk.viewRow(i2);
		// logger.debug(t2 + "\n" + x2);

		double dot = cosine(x1, x2);
		// logger.debug(t1 + " * " + t2 + " = " + dot);

		return dot;
	} // end compare

	/**
	 *
	 */
	public ScoreTermMap[] compareAll(String[] terms)
			throws TermNotFoundException {
		ScoreTermMap[] result = new ScoreTermMap[terms.length];

		for (int i = 0; i < terms.length; i++) {
			result[i] = compareAll(terms[i]);
			/*
			 * // print it Iterator it = result[i].entrySet().iterator(); while
			 * (it.hasNext()) { Map.Entry me = (Map.Entry) it.next(); Double dot
			 * = (Double) me.getKey(); String term = (String) me.getValue();
			 * System.out.println(terms[i] + " * " + term + " = " + dot); } //
			 * end while
			 */

		} // end for

		return result;
	} // end compareAll

	//
	public ScoreTermMap compareAll(String t) throws TermNotFoundException {
		// System.out.println("compare " + t + " to all");
		ScoreTermMap map = new ScoreTermMap(t, size);

		Iterator it = termIndex.termSet().iterator();
		while (it.hasNext()) {
			String t2 = (String) it.next();

			double dot = compare(t, t2);

			if (dot > threshold) {
				map.put(dot, t2);
				// System.out.println(t.substring(0, (t.length() - 1)) + " * " +
				// t2.substring(0, (t2.length() - 1)) + " = " + dot);
			}
		} // end for i

		return map;
	} // end similarity

	//
	public static void main(String[] args) throws Exception {
		String logConfig = System.getProperty("log-config");
		if (logConfig == null)
			logConfig = "log-config.txt";

		long begin = System.currentTimeMillis();

		PropertyConfigurator.configure(logConfig);

		if (args.length < 4) {
			System.out.println(getHelp());
			System.exit(1);
		}

		File Ut = new File(args[0] + "-Ut");
		File Sk = new File(args[0] + "-S");
		File r = new File(args[0] + "-row");
		File c = new File(args[0] + "-col");
		File df = new File(args[0] + "-df");
		double threshold = Double.parseDouble(args[1]);
		int size = Integer.parseInt(args[2]);

		String[] terms = new String[args.length - 3];

		for (int i = 0; i < terms.length; i++)
			terms[i] = args[i + 3];
		/*
		 * DomainTermSimilarity ts = new DomainTermSimilarity(Ut, Sk, r, c, df,
		 * threshold, size); ScoreTermMap[] map = ts.compareAll(terms);
		 * 
		 * for (int i=0;i<terms.length;i++) { System.out.println("***\n");
		 * System.out.println(map[i]); } // end for i
		 * 
		 * 
		 * long end = System.currentTimeMillis();
		 * System.out.println("term similarity calculated in " + (end - begin) +
		 * " ms");
		 */
		DomainTermSimilarity ts = new DomainTermSimilarity(Ut, Sk, r, c, df,
				threshold, size);
		ScoreTermMap map = ts.compareAll(terms[0]);

	//	System.out.println(map);

		long end = System.currentTimeMillis();
		System.out.println("term similarity calculated in " + (end - begin)
				+ " ms");
	} // end main

	/**
	 * Returns a command-line help.
	 * 
	 * return a command-line help.
	 */
	private static String getHelp() {
		StringBuffer sb = new StringBuffer();

		// License
		sb.append(License.get());

		// Usage
		sb
				.append("Usage: java -mx1024M org.fbk.it.hlt.jlsi.DomainTermSimilarity input threshold size (term)+\n\n");

		// Arguments
		sb.append("Arguments:\n");
		sb
				.append("\tinput\t\t-> root of files from which to read the term-by-document matrix (in sparse binary format), row index, col index and term document frequency\n");
		sb.append("\tthreshold\t-> similarity threshold\n");
		sb.append("\tsize\t\t-> number of similar terms to return\n");
		sb.append("\tterm\t\t-> input term\n");

		// Arguments
		// sb.append("Arguments:\n");

		return sb.toString();
	} // end getHelp

} // end class DomainTermSimilarity